<?php

use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\IOFactory;

class CFileSystem
{
    protected string $filename;
    protected mixed $stream;

    /**
     * @throws Exception
     */
    public function __construct(string $filename, string $mode = 'c+b', bool $use_include_path = false, $context = null)
    {
        $this->filename = $filename;
        if (is_dir($filename)) {
            $this->dir($filename, $context);
        } else {
            $this->fopen($filename, $mode, $use_include_path, $context);
        }
    }

    public function __destruct()
    {
        if (isset($this->stream)) fclose($this->stream);
    }


    /**
     * @param string $filename
     * @param string $mode
     * 'r(+)' 读(写) 文件头;
     * 'w(+)' 写(读) 文件头 大小截为零 创建;
     * 'a(+)' 写(读) 文件末尾 创建;
     * 'x(+)' 写(读) 文件头 创建 不允许文件存在;
     * 'c(+)' 写(读) 文件头 创建;
     * 用 'b' 或者用 't' 作为 mode 参数的最后一个字符。't' 可以透明地将 \n 转换为 \r\n;
     * @param bool $use_include_path
     * @param null $context
     * @throws Exception
     */
    function fopen(string $filename, string $mode, bool $use_include_path = false, $context = null): void
    {
        if (!is_readable($filename)) throw new Exception("[$filename]file is not readable");
        if (!is_writable($filename)) throw new Exception('file is not writable');
        $this->stream = fopen($filename, $mode, $use_include_path, $context);
    }

    function dir($directory, $context = null): bool|Directory
    {
        return dir($directory, $context);
    }

    /**
     * @param $stream
     * @param int $operation LOCK_SH LOCK_EX LOCK_UN LOCK_NB
     * @param $would_block
     * @return bool
     */
    function flock($stream, int $operation, &$would_block): bool
    {
        return flock($stream, $operation, $would_block);
    }


    /**
     * 读取文件的方式
     * readfile 读入一个文件并写入到输出缓冲
     * file 将整个文件读入一个数组中
     * file_get_contents 	将整个文件读入一个字符串
     * fgets 	从文件指针中读取一行
     * fgetss 	从文件指针中读取一行并过滤掉 HTML 和 PHP 标记;This function has been DEPRECATED as of PHP 7.3.0, and REMOVED as of PHP 8.0.0.
     * fgetc 	从文件指针中读取一个字符
     * fread 	从文件读取指定字节数的数据
     *
     */
    function read($mod = 'file_get_contents'): string
    {
        $fgets = function () {
            $content = '';
            while (false !== ($char = fgets($this->stream))) {
                $content .= "$char";
            }
            return $content;
        };
        return match ($mod) {
            'file_get_contents' => file_get_contents($this->filename),
            'readfile' => call_user_func(function () {
                ob_start();
                readfile($this->filename);
                $content = ob_get_contents();
                ob_end_clean();
                return $content;
            }),
            'fgetc' => (function () {
                $content = '';
                while (false !== ($char = fgetc($this->stream))) {
                    $content .= "$char";
                }
                return $content;
            })(),
            'fgets' => $fgets(),
            'fread' => (function () {
                $content = '';
                while (!feof($this->stream)) {
                    $content .= fread($this->stream, 8192);
                }
                return $content;
            })(),
            'file' => implode('', file($this->filename))
        };
    }

    function fseek(int $offset, int $whence = SEEK_SET): static
    {
        fseek($this->stream, $offset, $whence);
        return $this;
    }

    function write($content = ""): void
    {
        if (strlen($content)) fwrite($this->stream, $content);
    }

    function testScan($dir, $file, $scan)
    {
        $full = $dir . DIRECTORY_SEPARATOR. $file;
        if (is_dir($full) && $file != '.' && $file != '..') {
            //递归遍历
//                    $response[$full] = $scan($full);
            $response = $scan($full);
        } else {
            $response[] = $full;
        }
        return $response;
    }

    function scan($mod = 'scandir'): bool|array
    {
        $scan = function ($dir) use (&$scan) {
            $arr = scandir($dir);
            $response = [];
            foreach ($arr as $file) {
                $response = array_merge($response, $this->testScan($dir, $file, $scan));
            }
            return $response;
        };

        $opendir = function ($dir) use (&$opendir) {
            $dh = opendir($dir);
            $response = [];
            while (($file = readdir($dh)) !== false) {
                $response = array_merge($response, $this->testScan($dir, $file, $opendir));
            }
            closedir($dh);
            return $response;
        };

        $dir = function ($directory) use (&$dir) {
            $dh = dir($directory);
            $response = [];
            while ($file = $dh->read()) {
                $response = array_merge($response, $this->testScan($directory, $file, $dir));
            }
            $dh->close();
            return $response;
        };

        return match ($mod) {
            'scandir' => $scan($this->filename),
            'opendir' => $opendir($this->filename),
            'dir' => $dir($this->filename),
            default => true
        };
    }

    /**
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
     */
    function readXlsx($file, $startRowIndex = 1, $startColumnIndex = 1): array
    {
        $rows = [];
        $reader = IOFactory::createReader(IOFactory::READER_XLSX);
        $reader->setReadDataOnly(true);
        $spreadsheet = $reader->load($file);
        $worksheet = $spreadsheet->getActiveSheet();
        $row = $worksheet->getHighestRow();
        $column = $worksheet->getHighestColumn();
        $highestColumnIndex = Coordinate::columnIndexFromString($column);

        for ($r = $startRowIndex; $r <= $row; $r++) {
            $record = [];
            for ($i = $startColumnIndex;$i <= $highestColumnIndex;$i++) {
                $record[] = $worksheet->getCellByColumnAndRow($i, $r)->getValue();
            }
            $rows[] = $record;
        }

        return $rows;
    }
}